﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using QQ2564874169.Core;
using QQ2564874169.RelationalSql;
using Tests.RelationalSql.Mocks;

namespace Tests.RelationalSql
{
    [TestClass]
    public class 监听增删改操作
    {
        [TestMethod]
        public void 触发的对象和Before接口收到的对象内容相同()
        {
            var te1 = new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1};
            var te2 = new TestEntity {Id = 2, Name = "5", Result = 1};
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer()) {AfterDelay = 0};
            monitor.LoadService(typeof(TestBeforeByInsertUpdate));
            monitor.Before += (s, e) =>
            {
                if (!(e.Service is TestBeforeByInsertUpdate) || e.IsBeforeHandler == false)
                {
                    throw new TypeLoadException();
                }
                var model = e.Model as DbUpdateEventArgs;

                if (model != null)
                {
                    var where = ((DbUpdateEventArgs)e.Model).Where as TestEntity;
                    if (where != null && where.Id == te2.Id)
                    {
                        count++;
                    }
                }
                else if (e.Operation == DbExecuteOperation.Insert && ((TestEntity)e.Model).Id == te1.Id)
                {
                    count++;
                }
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(te1);
                    execute.Update(te2);
                }
            }
            monitor.Close();
            Assert.AreEqual(2, count);
        }

        [TestMethod]
        public void 无事务时每次操作立刻触发Before接口并且在事务中()
        {
            ISql sql = null;
            var count = 0;
            var container = new MonitorContainer();
            container.OnNewContainer += param =>
            {
                var item = param.First(i => i.Instance == sql);
                Assert.IsTrue(((ISql)item.Instance).InTransaction);
            };
            var monitor = new DbExecuteMonitor(container);
            monitor.LoadService(typeof(TestBeforeByInsertUpdate));
            monitor.Before += (s, e) =>
            {
                count++;
            };
            monitor.Start();

            using (sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(new TestEntity { Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1 });
                    Assert.AreEqual(1, count);

                    execute.Update(new TestEntity { Name = "5", Id = 1, Result = 1 });
                    Assert.AreEqual(2, count);
                }
            }

            monitor.Close();
        }

        [TestMethod]
        public void Before接口实例创建前会先注册单例Sql()
        {
            ISql sql = null;
            var count = 0;
            var container = new MonitorContainer();
            container.OnNewContainer += param =>
            {
                var item = param.First(i => i.Instance == sql);
                Assert.IsTrue(item.Scope == ServiceScope.Single);
            };
            var monitor = new DbExecuteMonitor(container);
            monitor.LoadService(typeof(TestBeforeByInsertUpdate));
            monitor.Before += (s, e) =>
            {
                count++;
            };
            monitor.Start();

            using (sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(new TestEntity { Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1 });
                    Assert.AreEqual(1, count);

                    execute.Update(new TestEntity { Name = "5", Id = 1, Result = 1 });
                    Assert.AreEqual(2, count);
                }
            }

            monitor.Close();
        }

        [TestMethod]
        public void 有事务时在正式提交前才触发Before接口()
        {
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer());
            monitor.Before += (s, e) =>
            {
                count++;
            };
            monitor.LoadService(typeof(TestBeforeByInsertUpdate));
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    using (var t = execute.BeginTransaction())
                    {
                        execute.Insert(new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1});
                        Assert.AreEqual(0, count);

                        using (var t2 = execute.BeginTransaction())
                        {
                            execute.Update(new TestEntity {Name = "5", Id = 1, Result = 1});
                            Assert.AreEqual(0, count);

                            t2.Commit();
                        }
                        Assert.AreEqual(0, count);

                        t.Commit();
                    }
                }
            }
            monitor.Close();
            Assert.AreEqual(2, count);
        }

        [TestMethod]
        public void 事务回滚不触发Before接口()
        {
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer());
            monitor.Before += (s, e) =>
            {
                count++;
            };
            monitor.LoadService(typeof(TestBeforeByInsertUpdate));
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    using (execute.BeginTransaction())
                    {
                        execute.Insert(new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1});
                        Assert.AreEqual(0, count);
                        execute.Update(new TestEntity {Name = "5", Id = 1, Result = 1});
                        Assert.AreEqual(0, count);
                    }
                }
            }
            monitor.Close();
            Assert.AreEqual(0, count);
        }

        [TestMethod]
        public void 一个Model触发多个Service的Before接口()
        {
            var map = new Dictionary<string, int>();
            var monitor = new DbExecuteMonitor(new MonitorContainer());
            monitor.LoadService(typeof(TestBeforeByInsertUpdate), typeof(TestBefore2), typeof(TestBefore3));
            monitor.Before += (s, e) =>
            {
                var name = e.Service.GetType().FullName;
                if (map.ContainsKey(name) == false)
                {
                    map.Add(name, 1);
                }
                else
                {
                    map[name] += 1;
                }
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1});
                    Assert.AreEqual(3, map.Count);
                    Assert.AreEqual(3, map.Values.Sum());

                    execute.Update(new TestEntity {Name = "5", Id = 1, Result = 1});
                    Assert.AreEqual(3, map.Count);
                    Assert.AreEqual(6, map.Values.Sum());
                }
            }
            monitor.Close();
        }

        [TestMethod]
        public void After的触发分为有延迟和无延迟两种情况()
        {
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer())
            {
                AfterDelay = 0
            };
            monitor.LoadService(typeof(TestAfterByInsertUpdate));
            monitor.Before += (s, e) =>
            {
                count++;
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(new TestEntity { Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1 });
                    Assert.AreEqual(1, count);

                    monitor.AfterDelay = 1000;
                    execute.Update(new TestEntity { Name = "5", Id = 1, Result = 1 });
                    Assert.AreEqual(1, count);
                    Task.Delay(monitor.AfterDelay*2).Wait();
                    Assert.AreEqual(2, count);

                    monitor.AfterDelay = 0;
                    execute.Insert(new TestEntity { Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1 });
                    Assert.AreEqual(3, count);
                }
            }
            monitor.Close();
        }

        [TestMethod]
        public void 触发的对象和After接口收到的对象内容相同()
        {
            var te1 = new TestEntity { Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1 };
            var te2 = new TestEntity { Id = 2, Name = "5", Result = 1 };
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer()) { AfterDelay = 0 };
            monitor.LoadService(typeof(TestAfterByInsertUpdate));
            monitor.Before += (s, e) =>
            {
                if (!(e.Service is TestAfterByInsertUpdate) || e.IsBeforeHandler)
                {
                    throw new TypeLoadException();
                }
                var model = e.Model as DbUpdateEventArgs;

                if (model != null)
                {
                    var where = ((DbUpdateEventArgs)e.Model).Where as TestEntity;
                    if (where != null && where.Id == te2.Id)
                    {
                        count++;
                    }
                }
                else if (e.Operation == DbExecuteOperation.Insert && ((TestEntity)e.Model).Id == te1.Id)
                {
                    count++;
                }
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(te1);
                    execute.Update(te2);
                }
            }
            monitor.Close();
            Assert.AreEqual(2, count);
        }

        [TestMethod]
        public void 无事务时每次操作立刻触发After接口()
        {
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer())
            {
                AfterDelay = 0
            };
            monitor.LoadService(typeof(TestAfterByInsertUpdate));
            monitor.Before += (s, e) =>
            {
                count++;
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1});
                    Assert.AreEqual(1, count);

                    execute.Update(new TestEntity {Name = "5", Id = 1, Result = 1});
                    Assert.AreEqual(2, count);
                }
            }
            monitor.Close();
        }

        [TestMethod]
        public void 有事务时在正式提交前才触发After接口()
        {
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer())
            {
                AfterDelay = 0
            };
            monitor.LoadService(typeof(TestAfterByInsertUpdate));
            monitor.Before += (s, e) =>
            {
                count++;
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    using (var t = execute.BeginTransaction())
                    {
                        execute.Insert(new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1});
                        Assert.AreEqual(0, count);

                        execute.Update(new TestEntity {Name = "5", Id = 1, Result = 1});
                        Assert.AreEqual(0, count);

                        t.Commit();
                    }
                }
            }
            monitor.Close();
            Assert.AreEqual(2, count);
        }

        [TestMethod]
        public void 事务回滚不触发After接口()
        {
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer())
            {
                AfterDelay = 0
            };
            monitor.LoadService(typeof(TestAfterByInsertUpdate));
            monitor.Before += (s, e) =>
            {
                count++;
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    using (execute.BeginTransaction())
                    {
                        execute.Insert(new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1});
                        execute.Delete(new TestEntity {Id = 3, Name = "4", CreateTime = DateTime.Now, Result = 1});
                        execute.Update(new TestEntity {Name = "5", Id = 1, Result = 1});
                    }
                }
            }
            monitor.Close();
            Assert.AreEqual(0, count);
        }

        [TestMethod]
        public void 一个Model触发多个Service的After接口()
        {
            var map = new Dictionary<string, int>();
            var monitor = new DbExecuteMonitor(new MonitorContainer())
            {
                AfterDelay = 0
            };
            monitor.LoadService(typeof(TestAfterByInsertUpdate), typeof(TestAfter2), typeof(TestAfter3));
            monitor.Before += (s, e) =>
            {
                var name = e.Service.GetType().FullName;
                if (map.ContainsKey(name) == false)
                {
                    map.Add(name, 1);
                }
                else
                {
                    map[name] += 1;
                }
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1});
                    execute.Update(new TestEntity {Name = "5", Id = 1, Result = 1});
                }
            }
            monitor.Close();
            Assert.AreEqual(3, map.Count);
            Assert.AreEqual(6, map.Values.Sum());
        }

        [TestMethod]
        public void 持久化只在After接口触发前执行()
        {
            var bcount = 0;
            var pcount = 0;
            var persister = new Persister();
            persister.OnSave += data =>
            {
                Assert.AreEqual(1, bcount);
                pcount++;
            };
            var container = new MonitorContainer();
            container.OnResolve += t => t == typeof(IPersister) ? persister : null;
            var monitor = new DbExecuteMonitor(container)
            {
                AfterDelay = 0
            };
            monitor.LoadService(typeof(TestAfterByInsertUpdate), typeof(TestBeforeByInsertUpdate));
            monitor.After += (s, e) =>
            {
                bcount++;
            };
            
            monitor.IsOpenPersist = true;
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1});
                }
            }
            monitor.Close();
            Assert.AreEqual(1, pcount);
            Assert.AreEqual(2, bcount);
        }

        [TestMethod]
        public void 测试AllBefore接口()
        {
            var te1 = new TestEntity {Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1};
            var te2 = new TestEntity2 {Id = 2, Name = "4", CreateTime = DateTime.Now, Result = 1};
            var te3 = new TestEntity3 {Id = 3, Name = "5", Result = 1};
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer())
            {
                AfterDelay = 0
            };
            monitor.LoadService(typeof(AllBefore));
            monitor.Before += (s, e) =>
            {
                if (!(e.Service is AllBefore))
                {
                    throw new TypeLoadException();
                }
                var model = e.Model as DbUpdateEventArgs;

                if (model != null)
                {
                    var where = ((DbUpdateEventArgs)e.Model).Where as TestEntity3;
                    if (where != null && where.Id == te3.Id)
                    {
                        count++;
                    }
                }
                else if (e.TableType == typeof(TestEntity) && ((TestEntity) e.Model).Id == te1.Id)
                {
                    count++;
                }
                else if (e.TableType == typeof(TestEntity2) && ((TestEntity2)e.Model).Id == te2.Id)
                {
                    count++;
                }
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(te1);
                    execute.Delete(te2);
                    execute.Update(te3);
                }
            }
            Assert.AreEqual(3, count);
            monitor.Close();
        }

        [TestMethod]
        public void 测试AllAfter接口()
        {
            var te1 = new TestEntity { Id = 1, Name = "2", CreateTime = DateTime.Now, Result = 1 };
            var te2 = new TestEntity2 { Id = 2, Name = "4", CreateTime = DateTime.Now, Result = 1 };
            var te3 = new TestEntity3 { Id = 3, Name = "5", Result = 1 };
            var count = 0;
            var monitor = new DbExecuteMonitor(new MonitorContainer())
            {
                AfterDelay = 0
            };
            monitor.LoadService(typeof(AllAfter));
            monitor.Before += (s, e) =>
            {
                if (!(e.Service is AllAfter))
                {
                    throw new TypeLoadException();
                }
                var model = e.Model as DbUpdateEventArgs;

                if (model != null)
                {
                    var where = ((DbUpdateEventArgs)e.Model).Where as TestEntity3;
                    if (where != null && where.Id == te3.Id)
                    {
                        count++;
                    }
                }
                else if (e.TableType == typeof(TestEntity) && ((TestEntity)e.Model).Id == te1.Id)
                {
                    count++;
                }
                else if (e.TableType == typeof(TestEntity2) && ((TestEntity2)e.Model).Id == te2.Id)
                {
                    count++;
                }
            };
            monitor.Start();
            using (var sql = new MockSql())
            {
                using (var execute = new MockDbExecute(sql))
                {
                    execute.Insert(te1);
                    execute.Delete(te2);
                    execute.Update(te3);
                }
            }
            Assert.AreEqual(3, count);
            monitor.Close();
        }

        public class TestBeforeByInsertUpdate : IWatchInsertBefore<TestEntity>, IWatchUpdateBefore<TestEntity>
        {
            public event Action<TestEntity> OnInsert;
            public event Action<TestEntity, TestEntity> OnUpdate;

            public void OnInsertBefore(TestEntity model)
            {
                OnInsert?.Invoke(model);
            }

            public void OnUpdateBefore(TestEntity setModel, TestEntity whereModel)
            {
                OnUpdate?.Invoke(setModel, whereModel);
            }
        }

        public class TestBefore2 : TestBeforeByInsertUpdate
        {

        }

        public class TestBefore3 : IWatchInsertBefore<TestEntity>, IWatchUpdateBefore<TestEntity>
        {
            public event Action<TestEntity> OnInsert;
            public event Action<TestEntity, TestEntity> OnUpdate;

            public void OnInsertBefore(TestEntity model)
            {
                OnInsert?.Invoke(model);
            }

            public void OnUpdateBefore(TestEntity setModel, TestEntity whereModel)
            {
                OnUpdate?.Invoke(setModel, whereModel);
            }
        }

        public class TestAfterByInsertUpdate : IWatchInsertAfter<TestEntity>, IWatchUpdateAfter<TestEntity>
        {
            public event Action<TestEntity> OnInsert;
            public event Action<TestEntity, TestEntity> OnUpdate;

            public void OnInsertAfter(TestEntity model)
            {
                OnInsert?.Invoke(model);
            }

            public void OnUpdateAfter(TestEntity setModel, TestEntity whereModel)
            {
                OnUpdate?.Invoke(setModel, whereModel);
            }
        }

        public class TestAfter2 : TestAfterByInsertUpdate
        {

        }

        public class TestAfter3 : IWatchInsertAfter<TestEntity>, IWatchUpdateAfter<TestEntity>
        {
            public event Action<TestEntity> OnInsert;
            public event Action<TestEntity, TestEntity> OnUpdate;

            public void OnInsertAfter(TestEntity model)
            {
                OnInsert?.Invoke(model);
            }

            public void OnUpdateAfter(TestEntity setModel, TestEntity whereModel)
            {
                OnUpdate?.Invoke(setModel, whereModel);
            }
        }

        public class Persister : IPersister
        {
            public event Action<PersistData> OnSave;
             
            public void Save(IEnumerable<PersistData> data)
            {
                foreach (var item in data)
                {
                    OnSave?.Invoke(item);
                }
            }

            public void Remove(PersistData data)
            {

            }

            public IEnumerable<PersistData> Load()
            {
                return null;
            }

            public void Dispose()
            {
                OnSave = null;
            }
        }

        public class TestEvnetArgs : EventArgs
        {
            public DbExecuteOperation Operation { get; set; }
            public object Model { get; set; }
        }

        public class AllBefore : IWatchBefore
        {
            public void OnBefore(DbExecuteOperation operation, Type tableType, object model)
            {

            }
        }

        public class AllAfter : IWatchAfter
        {
            public void OnAfter(DbExecuteOperation operation, Type tableType, object model)
            {

            }
        }
    }
}
